/* Copyright 2024 Coremail
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * MailDaoExecutor
 * 负责对Mail表进行操作
 */
import { GlobalContext } from '@ohos/dataorm'
import type { BaseDao, Property, QueryBuilder } from '@ohos/dataorm'
import type {
  IBuffer,
  MailBasic, MailDataAny, Order, Query } from '@coremail/mail_base';
import { MailAttribute, getLogger } from '@coremail/mail_base';
import { MailEntity } from '../../entities/MailEntity'
import { EntityTransformer } from '../EntityTransformer';
import { FileManager } from '../../utils/FileManger'
import { EmlFileManager } from '../EmlFileManager'
import Queue from '@ohos.util.Queue';
import type dataRs from '@ohos.data.relationalStore'
import filePreview from '@hms.filemanagement.filepreview';
import { FileUtil } from '../../utils/FileUtil';

const logger = getLogger("__sync");
// import fs from '@ohos.file.fs';

interface MailCheckResult {
  basic:boolean
  full:boolean
}
export class MailDaoImpl {
  private dao: BaseDao<MailEntity, number>
  private account:string
  private fileRootDir: string

  constructor(dao: BaseDao<MailEntity, number>, fileRootDir: string, account: string) {
    this.dao = dao;
    this.fileRootDir = fileRootDir;
    this.account = account;
  }

  /**
   * 本地是否具有这封邮件.
   * @deprecated using hasMailByRawSql instead.
   * @param mid
   * @returns
   */
  hasMail(mid: string,folderId:number): Promise<boolean> {
    return this.getMail(mid,folderId).then((ret: MailEntity | null) => {
      if (ret != null) {
        return true;
      }
      else {
        return false;
      }
    })
  }

  /**
   * 通过rawsql查询,理论上更快一点.
   * @param mid
   * @returns
   */
  async hasMailByRawSql(mid: string,folderId:number|null): Promise<boolean> {
    if (!mid || mid.length == 0) return false;
    let queryStr = `SELECT mid FROM mail WHERE mid = ? `
    let args:Array<string|number> = [mid];
    if(folderId != null) {
      queryStr += " AND folder_id = ? "
      args.push(folderId);
    }
    // 使用whereOr多条件查询无效，此处选择sql语句实现
    let cursor:dataRs.ResultSet = await this.dao.rawQuery(queryStr,args) as dataRs.ResultSet;
    let count = cursor.rowCount;
    // logger.debug(`[__MailCheck] [${mid}:${count}]`)
    return count > 0 ? true : false
  }


  /**
   * 检查传入邮件id列表的信息.
   *
   * @param mailIdList
   * @returns
   */
  async checkMailExist(mailIdList: string[],folderId:number): Promise<{
    basic: boolean,
    full: boolean
  }[]> {
    let retArr: MailCheckResult[] = [];
    for await (const mailId of mailIdList) {
      let hasBasic = false, isFull = false;
      let mail = await this.getMail(mailId,folderId);
      if (!mail) {
        hasBasic = false;
        isFull = false;
      }
      else {
        if (mail.emlPath && mail.emlPath.length > 0) {
          isFull = true;
        }
        //具有structure就说明有basic信息
        if (mail.structure && mail.structure.length > 0) {
          hasBasic = true;
        }
      }
      retArr.push({ basic: hasBasic, full: isFull })
    }
    return retArr;
  }

  /**
   * 传入一个mid的列表
   * 返回不存在的id列表.
   * SELECT mid FROM mail WHERE mid  in ('9181','9183')
   * @param mid
   * @returns
   */
  async hasMails(mids: string[]): Promise<string[]> {
    if(!mids || mids.length == 0) return [];
    let range = "( ";
    if(mids.length == 1){
      range += " '?' ";
    }
    else {
      range += mids.map((mid)=>{return " '?' "}).join(",");
    }
    range += " ) "
    let args = mids;
    let queryStr = `SELECT mid FROM mail WHERE mid IN ${range}`;
    // 使用whereOr多条件查询无效，此处选择sql语句实现
    let cursor = await this.dao.rawQuery(queryStr,args) as dataRs.ResultSet;
    let ret: string[] = [];
    if(cursor.goToFirstRow()){
      let str = cursor.getString(0);
      ret.push(str);
    }
    return ret;
  }



  async getCount(): Promise<number> {
    let pRet = null;
    let folderId = null;
    if (folderId == null) {
      pRet = this.dao.rawQuery(`SELECT COUNT(*) FROM mail`);
    }
    else if (folderId == 1) {
      pRet = this.dao.rawQuery(`SELECT COUNT(*) FROM mail WHERE _isNew = 1 AND _isStar = 1`);
    }
    else {
      pRet = this.dao.rawQuery(`SELECT COUNT(*) FROM mail WHERE _isNew = 1 AND _folderId = ${folderId}`)
    }
    ;
    let cursor = await pRet as dataRs.ResultSet;
    cursor.goToFirstRow()
    let sum:number = cursor.getLong(0);
    return sum;
  }

  /**
   * 获取指定mid的邮件.
   * 如果没有,抛出异常
   * @param mid
   * @returns
   */
  async getMail(mid: string,folderId:number): Promise<MailEntity | null> {
    let entityClass = GlobalContext.getContext().getValue(GlobalContext.KEY_CLS) as Record<string, Object>;
    let properties = entityClass.MailEntity as Record<string, Property>;
    let queryBuilder: QueryBuilder<MailEntity> = this.dao.queryBuilder().whereSql(properties['mid'].eqSql(mid),[properties['folderId'].eqSql(folderId)])
    let query = queryBuilder.buildSql();
    let arr = await query.listSql();
    if (arr.length >= 1) {
      return arr[0]
    }
    else {
      return null;
    }
  }



  updateMail(entity: MailEntity):Promise<void> {
    return this.dao.updateAsync(entity);
  }

  // /**
  //  * 更新邮件的属性.
  //  * @param mid
  //  */
  // setMailAttributes(mailId: string,folderId:number,  attributes: MailAttribute[], modifyType: '' | '+' | '-'): Promise<MailAttribute[]> {
  //   return this.getMail(mailId,folderId).then(async (ret: MailEntity | null) => {
  //     if (ret != null) {
  //       let mail = ret as MailEntity;
  //       mail.updateAttr(attributes, modifyType);
  //       await this.updateMail(mail);
  //       return mail.getAttr();
  //     }
  //   })
  // }

  /**
   *
   * 获取一个文件夹中的邮件.
   * @param _page
   * @param start 1代表从第一封开始,偏移量为0
   * @param size   一页的数据量,如果为0,则返回全部的.
   * @param handler
   */
  async getFolderMailListByFolderId(folderId: number, start: number, size: number, order: Order): Promise<MailEntity[]> {
    let entityClass = GlobalContext.getContext().getValue(GlobalContext.KEY_CLS) as Record<string, Object>;
    let properties = entityClass.MailEntity as Record<string, Property>;
    let queryBuilder = this.dao.queryBuilder()

    queryBuilder.whereSql(properties[`folderId`].eqSql(folderId))


    if (order.by == "date") {
      if (order.desc) {
        queryBuilder.orderDescSql(properties['receivedDate']);
      }
      else {
        queryBuilder.orderAscSql(properties['receivedDate']);
      }
    }
    else {
      if (order.desc) {
        queryBuilder.orderDescSql(properties['size']);
      }
      else {
        queryBuilder.orderAscSql(properties['size']);
      }
    }
    if (size != 0) {
      queryBuilder.offset(start-1)
      queryBuilder.limit(size)
    }

    try {
      let query = queryBuilder.buildSql()
      let arr: MailEntity[] = await query.listSql();
      return Promise.resolve(arr);
    } catch(e) {
      getLogger("dao").error("sql failed", e)
      return [];
    }
  }

  public async insertMail(mail:MailEntity): Promise<void> {
    await this.dao.insert(mail);
  }

  public insertMailsAsync(arr: MailDataAny[], folderId: number): Promise<void> {
    let entities = arr.map((data) => {
      return EntityTransformer.getMailEntity(data, folderId);
    })
    return this.dao.insertInTxArrAsync(...entities).catch((e:Error) => {
      //TODO: 插入错误.
      logger.warn(e.message);
      return Promise.reject("insert error")
    });
  }

  /**
   * 插入单封邮件.
   * 返回影响的条数还是自增id?
   *    * accountKey = md5(account)
   * mailKey = hash(mid);
   * /data/storage/el2/base/files/mail_db/MzA5NjAyMzkwQHFxLmNvbQ==/$key/eml/$key.eml
   * 保存文件夹id还是文件名?
   * 文件名,更新文件名的时候需要更新所有的folderFullName.
   * @param arr
   * @param folderName
   * @returns
   */
  public async insertMailWithId(mail: IBuffer, mid: string, folderId: number,folderFullName:string): Promise<void> {
    let entity = new MailEntity();
    //eml文件相对路径.
    let relativePath = EmlFileManager.getRelativeEmlPathForMail(this.account,mid,folderFullName);
    let filePath = `${this.fileRootDir}/${relativePath}`;
    await FileUtil.writeToFile(mail,filePath);
    entity.emlPath = filePath;
    logger.debug(`[MailDaoImpl] [insertMailWithId] [__pop3] [${filePath},${mail.getSizeRaw()}]`)
    entity.mid = mid;

    entity.folderId = folderId;
    try {
      return await this.insertOrUpdateMailWithBasic(entity,folderId);
    } catch(e) {
      logger.error("insert or replace failed", e);
    }
  }

  /**
   * 插入邮件basic
   * @param mailId
   * @param basic
   * @param folderId
   * @returns
   */
  public insertOrReplaceMailWithBasic(mailId:string,basic:MailBasic,folderId:number):Promise<void> {
    let mail = new MailEntity();
    mail.mid = mailId;
    mail.folderId = folderId
    mail.setMailBasic(basic);
    return this.insertOrUpdateMailWithBasic(mail,folderId);
  }

  /**
   * insert or update
   * @param mailId
   * @param basic
   * @param folderId
   * @returns
   */
  public async insertOrUpdateMailWithBasic(entity:MailEntity,folderId:number):Promise<void> {
    let mail = await this.getMail(entity.mid,folderId) as MailEntity|null;
    if(mail) {
      for (const key in entity) {
        if (entity[key] !== undefined) {
          //这里欺骗一下编译器是string类型的.
          mail[key] = entity[key] as string;
        }
      }
      await this.dao.updateAsync(mail);
    }
    else {
      await this.dao.insert(entity);
    }
  }





  /**
   * 数据库删除一封邮件
   * 首先根据mailId找到entity的id,然后删除.
   * 返回影响的记录数量.
   * @param mailId
   * @returns
   */
  async deleteMail(mailId: string,folderId:number): Promise<number> {
    let entity = await this.getMail(mailId,folderId);
    if (entity) {
      await this.dao.deleteAsync(entity);
      return 1;
    }
    else {
      return 0;
    }
  }

  async deleteMailByRawSql(mailId: string|null ,folderId:number|null): Promise<number> {
    let queryStr = `DELETE FROM mail WHERE `
    let conditions:string[] = [];
    let args:Array<string|number> = [];
    if(mailId != null) {
      conditions.push(" mid = ? ");
      args.push(mailId);
    }
    if(folderId != null) {
      conditions.push(" folder_id = ? ");
      args.push(folderId);
    }
    if(conditions.length == 0) {
      return Promise.reject("no condition when deleting");
    }
    let conditionStr = conditions.join(" AND ");
    queryStr += conditionStr;

    await this.dao.getDatabase().execSQL(queryStr,args);

  }


  /**
   * 查询邮件
   *
   * sender           发件人  from
   * receiver         收件人  to
   * cc               抄送
   * subject          主题   subject
   * attachmentNames  附件   attachment
   *
   * 这5个字段中,匹配关键字的邮件.
   *
   * @param query
   * @param pageSize
   * @param offset
   * @returns
   */
  async searchMail(query: Query, folderId:number|null = null, pageSize: number = 0, offset: number = 0): Promise<MailEntity[]> {
    // let keyword = query.text;
    let args = [];
    let tableName = 'mail';

    let queryStr:string = `SELECT * FROM ${tableName} WHERE 1 = 1`
    if(folderId != null ) {
      queryStr += " AND folder_id = ? "
      args.push(folderId)
    }

    let orConditions = [];
    if(query.fields.from != null) {
      orConditions.push(" sender LIKE ? ")
      orConditions.push(" cc LIKE ? ")
      orConditions.push(" bcc LIKE ? ")
      args.push(this.likeString(query.fields.from))
      args.push(this.likeString(query.fields.from))
      args.push(this.likeString(query.fields.from))
    }

    if(query.fields.to != null) {
      orConditions.push(" receiver LIKE ? ")
      args.push(this.likeString(query.fields.to))
    }
    if(query.fields.subject != null) {
      orConditions.push(" subject LIKE ? ")
      args.push(this.likeString(query.fields.subject))
    }
    if(query.fields.attachment != null) {
      orConditions.push(" attachment_names LIKE ? ")
      args.push(this.likeString(query.fields.attachment))
    }


    if (query.attrs && query.attrs.length > 0) {
      for (let attr of query.attrs) {
        if (attr == MailAttribute.Seen) {
          queryStr += ` AND is_new = ?`
          args.push(1);
        }
        else if (attr == MailAttribute.Flagged) {
          queryStr += ' AND is_flag = ?'
          args.push(1);
        }
      }
    }

    if(orConditions.length > 0) {
      let orConditionStr = orConditions.join(" OR ")
      queryStr += ` AND ( ${orConditionStr} ) `
    }


    if (pageSize > 0) {
      queryStr += ` OFFSET ?`
      args.push(offset)
      queryStr += ` LIMIT ? `
      args.push(pageSize)
    }

    //排序使用receiveDate的降序.
    queryStr += ' ORDER BY received_date DESC '

    // 使用whereOr多条件查询无效，此处选择sql语句实现
    let cursor = await this.dao.rawQuery(queryStr, args) as Promise<dataRs.ResultSet>;
    let arr = this.dao.convertCursor2Entity(cursor);
    return arr;
  }

  /**
   * 返回%包括的字符串,用于like查询.
   * @param str
   * @returns
   */
  likeString(str:string): string {
    return `%${str}%`
  }

  /**
   * 本地获取邮件的flag属性
   * TODO: 没有这份邮件时返回什么.
   * @param mailId
   * @returns
   */
  getMailAttributes(mailId: string,folderId:number): Promise<MailAttribute[]> {
    return this.getMail(mailId,folderId).then((ret: MailEntity | null) => {
      if (ret != null) {
        let mail = ret as MailEntity;
        return mail.getAttr();
      }
      return Promise.reject("no such mail");
    })
  }

  /**
   * 获取邮件在当前排序下的索引.
   * TODO: 需要改成使用rawSql进行查询优化速度.
   * @param folderId
   * @param mailId
   * @param order
   * @returns
   */
  async getMailIndex(folderId: number, mailId: string, order: Order): Promise<number> {
    let mails = await this.getFolderMailListByFolderId(folderId, 1, 0, order);
    let index = mails.findIndex((item) => {
      return (item.mid == mailId)
    });
    return index + 1;
  }
}